home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Mac Game Programming Gurus / TricksOfTheMacGameProgrammingGurus.iso / Book Chapters / 01 - Your First Game / Dungeon 1 / Dungeon1.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-04-01  |  11.7 KB  |  407 lines  |  [TEXT/MMCC]

  1.  
  2. /* Dungeon1 â€“ prototype game example for the Mac Game book*/
  3. /* By Ingemar Ragnemalm 1995*/
  4. /**/
  5. /* This program is the first, simplest version of our demo game for the Mac game programming */
  6. /* book. The game is a typical (though extremely simplified) dungeon-digging game,*/
  7. /* where the objective is to collect treasures and fight monsters.*/
  8.  
  9. /* Representation:*/
  10. /* The array tileArr holds nearly all information we need. telling what is in each space in the grid.*/
  11. /* Monsters and treasures are only represented this way. In a real game, you may wish to*/
  12. /* keep a list of all monster and treasure positions, to avoid scanning for them and to keep more*/
  13. /* information about each.*/
  14. /* The game also keeps an array that tells what spaces are known to the player (tilesKnown), and*/
  15. /* the player position (playerPosition), so we don't have to scan for it all the time.*/
  16. /* The combat system is extremely simple. If you try moving to an enemy, you have 60% chance to*/
  17. /* hit it and kill it. If an enemy tries to move to you, it has 50% chance to hit, reducing your hit points*/
  18. /* by one.*/
  19. /* */
  20. /* The game ends when the player dies or exits.*/
  21.  
  22.  
  23. #include <Sound.h>
  24.  
  25. /*Size of the array*/
  26. #define    kArraySizeH 15
  27. #define    kArraySizeV 12
  28.  
  29. /*Size of the tiles*/
  30. #define    kTileSizeH 32
  31. #define    kTileSizeV 32
  32.  
  33. /* A macro for taking the abs of a value */
  34. #define abs(x) (x>0?x:-x)
  35.  
  36. /* All the possible states of a tile (space in the dungeon) */
  37. typedef enum {empty, wall, player, enemy, tempEnemy, gold, exitPos} TileState;
  38.     
  39. /* The window pointer */
  40. WindowPtr myWindow;
  41.  
  42. /* Arrays describing the dungeon */
  43.  
  44. /* What tiles have we seen? */
  45. Boolean tileKnown[kArraySizeH][kArraySizeV];
  46. /* What does each tile contain? */
  47. TileState tileArray[kArraySizeH][kArraySizeV] = {
  48.     {wall, wall, wall, wall, wall, wall, wall, wall, wall, wall, wall, wall},
  49.     {wall,empty,empty,empty, wall, wall, wall, wall, wall, wall, wall, wall},
  50.     {wall,player,empty,empty,wall, wall, wall, wall, wall, wall, wall, wall},
  51.     {wall,empty,empty,empty, wall, wall,empty,enemy, gold, wall, wall, wall},
  52.     {wall, wall, wall,empty, wall, wall,empty,empty,empty, wall, wall, wall},
  53.     {wall, wall, wall,empty, wall, wall,empty, wall, wall, wall, wall, wall},
  54.     {wall, wall, wall,empty, wall, wall,empty, wall, wall, wall, wall, wall},
  55.     {wall, wall,empty,empty,empty,empty,empty,empty,empty,empty,empty, wall},
  56.     {wall, wall,enemy,empty, wall, wall, wall, wall, wall, wall,empty, wall},
  57.     {wall, wall,empty,empty, wall, wall, wall, wall, wall, wall,empty, wall},
  58.     {wall, wall, wall, wall, wall,empty,empty,exitPos,wall, wall,empty,wall},
  59.     {wall, wall, wall, wall, wall,empty, gold,empty, wall, wall,empty, wall},
  60.     {wall, wall, wall, wall, wall,empty, gold,empty,empty,empty,empty, wall},
  61.     {wall, wall, wall, wall, wall,empty,empty,empty, wall, wall, wall, wall},
  62.     {wall, wall, wall, wall, wall, wall, wall, wall, wall, wall, wall, wall}
  63.     };
  64. /* In this first version of the game, we initialize the entire array to a hard-coded
  65. layout. A real game would either generate the level randomly or read levels from
  66. resources. In later versions, we will do it randomly. */
  67.  
  68.  
  69. /* Variables describing the player:*/
  70. Point playerPosition = {1,2};    /* Must match the tile set to "player" */
  71. short playerHitPoints = 5;
  72.  
  73. /* A boolean telling if we should quit yet or not */
  74. Boolean gDone = false;
  75.  
  76. /* Pictures*/
  77. PicHandle floorTile;
  78. PicHandle playerTile;
  79. PicHandle enemyTile;
  80. PicHandle goldTile;
  81. PicHandle wallTile;
  82. PicHandle exitTile;
  83.  
  84. /*All 8 directions as vectors*/
  85. Point directionTable[8] =
  86. {
  87.     { 0, 1 },
  88.     {-1, 1 },
  89.     {-1, 0 },
  90.     {-1,-1 },
  91.     { 0,-1 },
  92.     { 1,-1 },
  93.     { 1, 0 },
  94.     { 1, 1 }
  95. };
  96.  
  97.  
  98. /* A function that generates a value in the interval 0..range-1 */
  99.  
  100. static short Rand(short range)
  101. {
  102.     return (Random () & 0x7fff) % range;
  103. }; /*Rand*/
  104.  
  105.  
  106. /* Draw a tile */
  107.  
  108. static void DrawTile(short h, short v)
  109. {
  110.     Rect tileRectangle;
  111.  
  112.     SetRect(&tileRectangle, h * kTileSizeH, v * kTileSizeV, (h + 1) * kTileSizeH, (v + 1) * kTileSizeV);
  113.     if ( tileKnown[h][v] )
  114.         switch ( tileArray[h][v] )
  115.         {
  116.             case empty: 
  117.                 DrawPicture(floorTile, &tileRectangle); break;
  118.             case wall: 
  119.                 DrawPicture(wallTile, &tileRectangle); break;
  120.             case player: 
  121.                 DrawPicture(playerTile, &tileRectangle); break;
  122.             case enemy:
  123.             case tempEnemy: 
  124.                 DrawPicture(enemyTile, &tileRectangle); break;
  125.             case gold: 
  126.                 DrawPicture(goldTile, &tileRectangle); break;
  127.             case exitPos: 
  128.                 DrawPicture(exitTile, &tileRectangle); break;
  129.             default:
  130.                 PaintRect(&tileRectangle);
  131.         }
  132.     else
  133.         PaintRect(&tileRectangle);
  134. } /*DrawTile*/
  135.  
  136.  
  137. /* Set all tiles around the player to be known, and draw them if they were not known before. */
  138.  
  139. static void ShowAroundPlayer()
  140. {
  141.     short h, v;
  142.  
  143.     for ( h = playerPosition.h - 1 ; h <= playerPosition.h + 1 ; h++)
  144.         for ( v = playerPosition.v - 1 ; v <= playerPosition.v + 1 ; v++)
  145.             if ( ! tileKnown[h][v] )
  146.             {
  147.                 tileKnown[h][v] = true;
  148.                 DrawTile(h, v);
  149.             }
  150. } /*ShowAroundPlayer*/
  151.  
  152.  
  153. /* Move an enemy */
  154.  
  155. static void MoveEnemy(short h, short v)
  156. {
  157.     short dist;
  158.     short newh, newv;
  159.     short dir;
  160.  
  161. /*1: decide if we are close to enough to the player to "hear" the player*/
  162.  
  163.     dist = abs(h - playerPosition.h) + abs(v - playerPosition.v); /*City Block distance*/
  164.  
  165. /*2: Make a suggested destination, newh, newv*/
  166.  
  167.     if ( dist < Rand(15) )                /*Move towards the player*/
  168.     {
  169.         if ( h < playerPosition.h )
  170.             newh = h + 1;
  171.         else if ( h > playerPosition.h )
  172.             newh = h - 1;
  173.         else
  174.             newh = h;
  175.         if ( v < playerPosition.v )
  176.             newv = v + 1;
  177.         else if ( v > playerPosition.v )
  178.             newv = v - 1;
  179.         else
  180.             newv = v;
  181.     }
  182.     else                                /*Move randomly*/
  183.     {
  184.         dir = Rand(8);
  185.         newh = h + directionTable[dir].h;
  186.         newv = v + directionTable[dir].v;
  187.     };
  188.  
  189. /*3: Check what is in the destination and take appropriate action (move, fight)*/
  190.  
  191.     switch ( tileArray[newh][newv] )
  192.     {
  193.         case empty: 
  194.             tileArray[newh][newv] = tempEnemy;    /*We can't use "enemy", since then we might process it again in the same move!*/
  195.             tileArray[h][v] = empty;
  196.             DrawTile(newh, newv);
  197.             DrawTile(h, v);
  198.             break;
  199.         case player: 
  200.             if ( Rand(10) > 5 )                    /*Does it hit?*/
  201.             {                                /*Enemy hits player!*/
  202.                 playerHitPoints--;            /*Reduce player hit points*/
  203.                 if ( playerHitPoints > 0 )
  204.                 {                            /*Player still lives!*/
  205.                     if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pPlayer hit"), false) )
  206.                         ;
  207.                 }
  208.                 else
  209.                 {                            /*Player died!*/
  210.                     if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pPlayer died"), false) )
  211.                         ;
  212.                     gDone = true;            /* Game over - quit! */
  213.                 };
  214.             }
  215.             else
  216.             {                                /*Miss!*/
  217.                 if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pPlayer miss"), false) )
  218.                     ;
  219.             };
  220.             break;
  221.          case wall:
  222.         case enemy:
  223.         case gold:
  224.         case exitPos:
  225.         case tempEnemy: 
  226.             ;                                /*Don't move to any of these!*/
  227.     }; /*case*/
  228. } /*MoveEnemy*/
  229.  
  230.  
  231. /* Initialize - create window, load graphics */
  232.  
  233. static void InitDungeon()
  234. {
  235.     Rect windowRectangle;
  236.  
  237. /*Set up the window*/
  238.     SetRect(&windowRectangle, 50, 50, 50 + kArraySizeH * kTileSizeH, 50 + kArraySizeV * kTileSizeV);
  239.     myWindow = NewCWindow(nil, &windowRectangle, "\pDungeon 1", true, 0, (WindowPtr)-1L, false, 0);
  240.     SetPort(myWindow);
  241.  
  242.     qd.randSeed = TickCount ();    /*Seed the random number generator*/
  243.  
  244. /*Load all pictures*/
  245.     floorTile = GetPicture(128);            /*PICT resource #128.*/
  246.     playerTile = GetPicture(129);            /*PICT resource #129.*/
  247.     enemyTile = GetPicture(130);            /*PICT resource #130.*/
  248.     goldTile = GetPicture(131);                /*PICT resource #131.*/
  249.     wallTile = GetPicture(132);                /*PICT resource #132.*/
  250.     exitTile = GetPicture(133);                /*PICT resource #133.*/
  251. } /*InitDungeon*/
  252.  
  253.  
  254. /* ValidMove checks if a tile clickedTile is inside the array bounds *and* near the player */
  255.  
  256. static Boolean ValidMove(Point clickedTile)
  257. {
  258. /* Valid tile?*/
  259.     if ( clickedTile.h >= 0 )
  260.         if ( clickedTile.v >= 0 )
  261.             if ( clickedTile.h < kArraySizeH )
  262.                 if ( clickedTile.v < kArraySizeV )
  263. /* OK, we are inside the game area, clicking in some space! Is it next to the player?*/
  264.                     if ( clickedTile.h >= playerPosition.h - 1 )
  265.                         if ( clickedTile.h <= playerPosition.h + 1 )
  266.                             if ( clickedTile.v >= playerPosition.v - 1 )
  267.                                 if ( clickedTile.v <= playerPosition.v + 1 ) 
  268.                                     return true;
  269.     return false;
  270. } /*ValidMove*/
  271.  
  272.  
  273. /* Try to move the player to the position where we clicked. */
  274.  
  275. static void MovePlayer(Point clickedTile)
  276. {
  277. short h, v;
  278.  
  279. /* Valid move?*/
  280.     if (ValidMove(clickedTile))
  281. /* Yes! What is there? */
  282.     { 
  283.         switch ( tileArray[clickedTile.h][clickedTile.v] )
  284.             {
  285.             case empty:
  286.                     tileArray[playerPosition.h][playerPosition.v] = empty;
  287.                     tileArray[clickedTile.h][clickedTile.v] = player;
  288.                     DrawTile(playerPosition.h, playerPosition.v);
  289.                     DrawTile(clickedTile.h, clickedTile.v);
  290.                     playerPosition = clickedTile;
  291.                     break;
  292.             case gold: 
  293.                     tileArray[playerPosition.h][playerPosition.v] = empty;
  294.                     tileArray[clickedTile.h][clickedTile.v] = player;
  295.                     DrawTile(playerPosition.h, playerPosition.v);
  296.                     DrawTile(clickedTile.h, clickedTile.v);
  297.                     playerPosition = clickedTile;
  298. /* We could add score here */
  299.                     if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pMoney"), false) )
  300.                         ;
  301.                     break;
  302.             case wall: 
  303.                 SysBeep(1);
  304.                 break;
  305.             case enemy: 
  306.                 if ( Rand(10) > 4 )
  307.                     { /*Hit! Play a "monster died" sound*/
  308.                         if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pEnemy died"), false) )
  309.                             ;
  310.                         /* Walk to the space where the monster was.*/
  311.                         tileArray[playerPosition.h][playerPosition.v] = empty;
  312.                         tileArray[clickedTile.h][clickedTile.v] = player;
  313.                         DrawTile(playerPosition.h, playerPosition.v);
  314.                         DrawTile(clickedTile.h, clickedTile.v);
  315.                         playerPosition = clickedTile;
  316.                     }
  317.                 else
  318.                     { /*Miss! Play the "miss" sound. */
  319.                         if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pEnemy miss"), false) )
  320.                             ;
  321.                     };
  322.                 break;
  323.                 /*attack!*/
  324.             case exitPos: 
  325.                 tileArray[playerPosition.h][playerPosition.v] = empty;
  326.                 tileArray[clickedTile.h][clickedTile.v] = player;
  327.                 DrawTile(playerPosition.h, playerPosition.v);
  328.                 DrawTile(clickedTile.h, clickedTile.v);
  329.                 playerPosition = clickedTile;
  330.                 if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pNext level"), false) )
  331.                     ;
  332.                 gDone = true;
  333.                 break;
  334.         }; /*case*/
  335.     };
  336.  
  337. ShowAroundPlayer();
  338.  
  339. /* Monsters are allowed to move now!*/
  340.  
  341. /* Move all enemies!*/
  342. for ( h = 0 ; h < kArraySizeH ; h++)
  343.     for ( v = 0 ; v < kArraySizeV ; v++)
  344.         if ( tileArray[h][v] == enemy )
  345.             MoveEnemy(h, v);
  346. /* After moving, replace tempEnemy by enemy.*/
  347. for ( h = 0 ; h < kArraySizeH ; h++)
  348.     for ( v = 0 ; v < kArraySizeV ; v++)
  349.         if ( tileArray[h][v] == tempEnemy )
  350.             tileArray[h][v] = enemy;
  351. } /*MovePlayer*/
  352.  
  353.  
  354. /* Standard inits */
  355.  
  356. static void InitToolbox(void)
  357. {
  358.     InitGraf (&qd.thePort);
  359.     InitFonts ();
  360.     FlushEvents (everyEvent,0);
  361.     InitWindows ();
  362.     InitMenus ();
  363.     TEInit ();
  364.     InitDialogs (nil);
  365.     InitCursor ();
  366. } /*InitToolbox*/
  367.  
  368.  
  369. /* Main program */
  370.  
  371. void main(void)
  372. {
  373.     Point     clickPoint, clickedTile;
  374.     short    h, v;
  375.  
  376.     InitToolbox();
  377.     InitDungeon();
  378.  
  379. /*Draw all tiles!*/
  380.     for ( h = 0 ; h < kArraySizeH ; h++)
  381.         for ( v = 0 ; v < kArraySizeV ; v++)
  382.             DrawTile(h, v);
  383.  
  384.     ShowAroundPlayer();
  385.  
  386. /*Initializations done! Run the game loop until the game ends.*/
  387.     do
  388.     {
  389.         if (Button()) {
  390.         GetMouse(&clickPoint);        /* Get the position of the click */
  391.         clickedTile.h = clickPoint.h / kTileSizeH;    /* Convert to grid. */
  392.         clickedTile.v = clickPoint.v / kTileSizeV;
  393.         MovePlayer(clickedTile);    /* Try to move there */
  394.         do {} while (Button());        /* Wait until the mouse click ends */
  395.         };
  396.     } while (!  gDone);
  397.  
  398.     FlushEvents(mDownMask, 0);        /* Get rid of mouse down events! */
  399. } /*Dungeon main program*/
  400.  
  401.  
  402. /* What's left for making a real game of it?*/
  403. /* - Animations*/
  404. /* - Several levels*/
  405. /* - Faster drawing*/
  406. /* - Asynch sound*/
  407. /* - More objects, i.e. weapons, monsters, treasures…*/